<?php
/**
 * 
 * 用数据库表来锁表
 * @author 张军磊 <zhangjunlei26@gmail.com>
 *
 *

 **/
class Lock_Driver_Db extends Lock
{

	protected $cli_only = FALSE; // 仅命令行交互(CLI)运行级锁，如果设定为TRUE,排斥进程号不同的相同锁运行
	protected $db;

	public function __construct ($options = array())
	{
		parent::__construct($options);
		$this->db = func::get_db();
		$this->__init_table();
	}

	/**
	 * 初始化锁表结构
	 *
	 * @throws Exception
	 */
	protected function __init_table ()
	{
		$exists = $this->db->query("SHOW TABLES LIKE 'sys_locks'");
		if (! $exists)
		{
			$sql = <<<DOC
CREATE TABLE `sys_lock` (
  `key` char(32) NOT NULL COMMENT '#锁主键',
  `pid` int(10) unsigned NOT NULL COMMENT '#进程ID',
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '#锁定时间',
  `desc` varchar(512) DEFAULT NULL COMMENT '#锁描述',
  PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DOC;
			$this->db->exec($sql);
		}
	}

	/**
	 * 加锁
	 *
	 * @param $key string       	
	 * @param $data string       	
	 * @param $auto_unlock boolean       	
	 */
	public function lock ($key ,$data = NULL ,$auto_unlock = TRUE)
	{
		$current_pid = $this->get_pid();
		// 防止用户中断破坏锁机制
		ignore_user_abort();
		// ON DUPLICATE KEY UPDATE ctime=NOW()
		$sql = "INSERT INTO `sys_lock` (`key`,`pid`,`desc`) VALUES (?,?,?)";
		$rs = $this->db->exec($sql, $key, $current_pid, $data);
		// 加锁未成功，再判断是否超时
		if (! $rs)
		{
			// 设定事务级别为读共享，避免超时锁取时间有冲突
			$this->db->set_level();
			$this->db->beginTransaction();
			// 锁定超时判断
			$sql = "SELECT ((NOW()-ctime>{$this->timeout})) as `timeout` from sys_lock WHERE `key`=? LIMIT 1;";
			$rs = $this->db->query($sql, $key);
			// 前锁未超时
			if (! $rs) return FALSE;
			// 前锁超时，更新锁信息
			$sql = "UPDATE `sys_lock` SET `pid`=?,ctime=NOW(),`desc`=? WHERE `key`=? AND (NOW()-ctime>{$this->timeout})";
			$rs = $this->db->exec($sql, $current_pid, $data, $key);
			// 更新失败，按未锁定处理
			if (! $rs)
			{
				$this->db->rollBack();
				return FALSE;
			}
			// 提交事务
			$this->db->commit();
		}
		// 自动解锁
		if ($auto_unlock)
		{
			$this->locked[$key] = $data; // 自动销毁设置
		}
		// 返回锁定成功
		return TRUE;
	}

	/**
	 * 取已经存在的锁的描述内容
	 *
	 * @param $key string       	
	 */
	public function get_desc ($key)
	{
		return $this->db->query("SELECT `desc` FROM sys_lock WHERE `key`=? LIMIT 1;", $key);
	}

	/**
	 * 解锁
	 *
	 * @param $key string       	
	 */
	public function unlock ($key)
	{
		$sql = "DELETE FROM sys_lock WHERE `key`=? LIMIT 1;";
		$this->db->exec($sql, $key);
		return TRUE;
	}

	/**
	 * 获取当前的进程id约定8位，不够补0
	 */
	protected function get_pid ()
	{
		return function_exists('posix_getpid') ? posix_getpid() : getmypid();
	}
}